#region Copyright ?2010 Pawel Idzikowski [idzikowski@sharpserializer.com]

//  ***********************************************************************
//  Project: sharpSerializer
//  Web: http://www.sharpserializer.com
//
//  This software is provided 'as-is', without any express or implied warranty.
//  In no event will the author(s) be held liable for any damages arising from
//  the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//      1. The origin of this software must not be misrepresented; you must not
//        claim that you wrote the original software. If you use this software
//        in a product, an acknowledgment in the product documentation would be
//        appreciated but is not required.
//
//      2. Altered source versions must be plainly marked as such, and must not
//        be misrepresented as being the original software.
//
//      3. This notice may not be removed or altered from any source distribution.
//
//  ***********************************************************************

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using CuteAnt.Serialization.Advanced.Serializing;
using CuteAnt.Serialization.Advanced.Xml;

namespace CuteAnt.Serialization.Advanced
{
	/// <summary>Reads data which was stored with DefaultXmlWriter</summary>
	public sealed class DefaultXmlReader : IXmlReader
	{
		private readonly XmlReaderSettings _settings;
		private readonly ITypeNameConverter _typeNameConverter;
		private readonly ISimpleValueConverter _valueConverter;
		private XmlReader _currentReader;
		private Stack<XmlReader> _readerStack;

		///<summary>
		///</summary>
		///<param name = "typeNameConverter"></param>
		///<param name = "valueConverter"></param>
		///<param name = "settings"></param>
		///<exception cref = "ArgumentNullException"></exception>
		public DefaultXmlReader(ITypeNameConverter typeNameConverter, ISimpleValueConverter valueConverter,
														XmlReaderSettings settings)
		{
			ValidationHelper.ArgumentNull(typeNameConverter, "typeNameConverter");
			ValidationHelper.ArgumentNull(valueConverter, "valueConverter");
			ValidationHelper.ArgumentNull(settings, "settings");
			_typeNameConverter = typeNameConverter;
			_valueConverter = valueConverter;
			_settings = settings;
		}

		#region IXmlReader Members

		/// <summary>Reads next valid element</summary>
		/// <returns>null if nothing was found</returns>
		public String ReadElement()
		{
			while (_currentReader.Read())
			{
				if (_currentReader.NodeType != XmlNodeType.Element) { continue; }
				return _currentReader.Name;
			}
			return null;
		}

		/// <summary>Reads all sub elements of the current element</summary>
		/// <returns></returns>
		public IEnumerable<String> ReadSubElements()
		{
			// Position the reader on an element
			_currentReader.MoveToElement();

			// create the subReader
			XmlReader subReader = _currentReader.ReadSubtree();

			// positions the new XmlReader on the node that was current before the call to ReadSubtree method
			// http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k%28SYSTEM.XML.XMLREADER.READSUBTREE%29;k%28TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV2.0%22%29;k%28DevLang-CSHARP%29&rd=true
			subReader.Read();
			pushCurrentReader(subReader);

			try
			{
				// read the first valid element
				String name = ReadElement();

				// read further elements
				while (!name.IsNullOrWhiteSpace())
				{
					yield return name;
					name = ReadElement();
				}
			}
			finally
			{
				// Close the current reader,
				// it positions the parent reader on the last node of the subReader
				subReader.Close();

				// aktualise the current Reader
				popCurrentReader();
			}
		}

		/// <summary>Reads attribute as String</summary>
		/// <param name = "attributeName"></param>
		/// <returns>null if nothing was found</returns>
		public String GetAttributeAsString(String attributeName)
		{
			if (!_currentReader.MoveToAttribute(attributeName)) { return null; }
			return _currentReader.Value;
		}

		/// <summary>Reads attribute and converts it to type</summary>
		/// <param name = "attributeName"></param>
		/// <returns>null if nothing found</returns>
		public Type GetAttributeAsType(String attributeName)
		{
			String typeName = GetAttributeAsString(attributeName);
			return _typeNameConverter.ConvertToType(typeName);
		}

		/// <summary>Reads attribute and converts it to integer</summary>
		/// <param name = "attributeName"></param>
		/// <returns>0 if nothing found</returns>
		public Int32 GetAttributeAsInt(String attributeName)
		{
			if (!_currentReader.MoveToAttribute(attributeName)) { return 0; }
			return _currentReader.ReadContentAsInt();
		}

		/// <summary>Reads attribute and converts it as array of Int32</summary>
		/// <param name = "attributeName"></param>
		/// <returns>empty array if nothing found</returns>
		public Int32[] GetAttributeAsArrayOfInt(String attributeName)
		{
			if (!_currentReader.MoveToAttribute(attributeName)) { return null; }
			return getArrayOfIntFromText(_currentReader.Value);
		}

		/// <summary>Reads attribute and converts it to object of the expectedType</summary>
		/// <param name = "attributeName"></param>
		/// <param name = "expectedType"></param>
		/// <returns></returns>
		public object GetAttributeAsObject(String attributeName, Type expectedType)
		{
			String objectAsText = GetAttributeAsString(attributeName);
			return _valueConverter.ConvertFromString(objectAsText, expectedType);
		}

		/// <summary>Open the stream</summary>
		/// <param name = "stream"></param>
		public void Open(Stream stream)
		{
			_readerStack = new Stack<XmlReader>();
			XmlReader reader = XmlReader.Create(stream, _settings);

			// set the main reader
			pushCurrentReader(reader);
		}

		/// <summary>Open the Uri</summary>
		/// <param name="inputUri" type="string">
		/// <para></para>
		/// </param>
		public void Open(String inputUri)
		{
			_readerStack = new Stack<XmlReader>();
			XmlReader reader = XmlReader.Create(inputUri, _settings);

			// set the main reader
			pushCurrentReader(reader);
		}

		/// <summary>Open the TextReader</summary>
		/// <param name="input" type="System.IO.TextReader">
		/// <para></para>
		/// </param>
		public void Open(TextReader input)
		{
			_readerStack = new Stack<XmlReader>();
			XmlReader reader = XmlReader.Create(input, _settings);

			// set the main reader
			pushCurrentReader(reader);
		}

		/// <summary>Stream can be further used</summary>
		public void Close()
		{
			_currentReader.Close();
		}

		#endregion

		/// <summary>Remove one reader from stack and reset the current reader</summary>
		private void popCurrentReader()
		{
			// Remove one reader from the stack
			if (_readerStack.Count > 0)
			{
				_readerStack.Pop();
			}
			if (_readerStack.Count > 0)
			{
				_currentReader = _readerStack.Peek();
				return;
			}
			_currentReader = null;
		}

		/// <summary>Add reader to stack and set it the current reader</summary>
		/// <param name = "reader"></param>
		private void pushCurrentReader(XmlReader reader)
		{
			_readerStack.Push(reader);
			_currentReader = reader;
		}

		/// <summary>Converts text in form "1,2,3,4,5" to Int32[] {1,2,3,4,5}</summary>
		/// <param name = "text"></param>
		/// <returns>null if no items are recognized or the text is null or empty</returns>
		private static Int32[] getArrayOfIntFromText(String text)
		{
			if (text.IsNullOrWhiteSpace()) { return null; }
			String[] splittedString = text.Split(new[] { ',' });
			if (splittedString.Length == 0) { return null; }
			var result = new List<Int32>();

			foreach (String s in splittedString)
			{
				Int32 i = Int32.Parse(s);
				result.Add(i);
			}
			return result.ToArray();
		}
	}
}